package com.XZDP.utils;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.XZDP.entity.RedisData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

//基于StringRedisTemplate封装一个缓存工具类，满足下列需求：
//方法1：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置TTL过期时间
//方法2：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置逻辑过期时间，用于处理缓存击穿问题
//方法3：根据指定的key查询缓存，并反序列化为指定类型
//方法4：根据指定的key查询缓存，并反序列化为指定类型，需要利用逻辑过期解决缓存击穿问题

//全部是转JOSN的,不包含MAP结构
@Component
@Slf4j
public class CacheUtils {
    @Autowired
    private MyUtils myUtils;
    private StringRedisTemplate redisTemplate;
    private static final ExecutorService Thread_pools = Executors.newFixedThreadPool(10);


    public CacheUtils(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    //将任意Java对象序列化为json并存储在string类型的key中，并且可以设置TTL过期时间

    /**
     * @param key     传入缓存的key
     * @param value   传入任意对象
     * @param timeout 传入任意过期时间数值
     * @param unit    传入任意过单位
     */
    public void SaveToRedisWithTime(String key, Object value, int timeout, TimeUnit unit) throws JsonProcessingException {
        //将value转换成string
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(value);
        redisTemplate.opsForValue().set(key, s);
        redisTemplate.expire(key, timeout, unit);
    }

    public void SaveToRedis(String key, Object value) throws JsonProcessingException {
        //将value转换成string
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(value);
        redisTemplate.opsForValue().set(key, s);
    }


    //将任意Java对象序列化为json并存储在string类型的key中，并且可以设置逻辑过期时间，用于处理缓存击穿问题

    /**
     * @param key
     * @param value
     * @param timeout 逻辑过期时间
     * @throws JsonProcessingException
     */
    public void SaveToRedisWithLogicalExpiration(String key, Object value, Long timeout) throws JsonProcessingException {
        RedisData redisData = new RedisData();
        redisData.setExpired(System.currentTimeMillis() + timeout);
        redisData.setData(value);
        SaveToRedis(key, redisData);
    }

    //根据指定的key查询缓存，并反序列化为指定类型,实现缓存重建(通用redis查询，不包含缓存击穿解决方案)

    /**
     * @param key      缓存的key前缀
     * @param type     返回值类型
     * @param id       查询条件/拼接key
     * @param function 查询数据库函数逻辑
     * @param <T>      返回值类型
     * @param <ID>     传入的查询条件类型
     * @return
     */
    public <T, ID> T GetRedisByKey(String key, Class<T> type, ID id, Function<ID, T> function) throws JsonProcessingException {
        //查询缓存中是否有值
        String s = redisTemplate.opsForValue().get(key + id);
        if (s != null) {
            //存在值进行反序列化后返回
            T bean = JSONUtil.toBean(s, type);
            return bean;
        }
        //缓存中不存在值,查询数据库
        T bean = function.apply(id);
        //缓存重建
        SaveToRedis(key + id, bean);
        //返回这个对象
        return bean;
    }

    // 根据指定的key查询缓存，并反序列化为指定类型，需要利用逻辑过期解决缓存击穿问题

    /**
     * @param key      key的前缀
     * @param type     返回值类型
     * @param id
     * @param function 传入方法函数
     * @param timeout  逻辑过期时间
     * @param <T>      返回值类型
     * @param <ID>     传入的查询条件类型
     * @return
     */
    public <T, ID> T GetRedisWithLogicalExpiration(String key, Class<T> type, ID id, Function<ID, T> function, Long timeout, String Lock_Key) {
        String s = redisTemplate.opsForValue().get(key + id);
        if (s == null) {
            return null;
        }
        //进行反序列化
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        JSONObject Bean = (JSONObject) redisData.getData();
        T bean = JSONUtil.toBean(Bean, type);
        //判断是缓存是否过期
        Long time = System.currentTimeMillis();
        if (time < redisData.getExpired()) {
            //缓存未过期,反序列化后直接返回
            return bean;
        }
        //缓存过期,尝试获取锁进行缓存重建
        Boolean b = myUtils.tryGetLock(Lock_Key);
        if (b) {
            //获取到锁
            log.info("获取锁成功");
            //线程池开启新线程进行缓存重建
            Thread_pools.submit(() -> {
                try {
                    //数据库查询新数据
                    T Newbean = function.apply(id);
                    //进行缓存重建
                    SaveToRedisWithLogicalExpiration(key + id, Newbean, timeout);
                    log.info("缓存重建成功");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    myUtils.CloseLock("Lock:shop");
                    log.info("释放锁成功");
                }
            });
            return bean;
        }
        //未获取到锁,直接返回旧的数据
        return bean;
    }
}
